// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;

namespace System.Net.Http.Headers
{
    public class RangeHeaderValue : ICloneable
    {
        private string _unit;
        private UnvalidatedObjectCollection<RangeItemHeaderValue>? _ranges;

        public string Unit
        {
            get { return _unit; }
            set
            {
                HeaderUtilities.CheckValidToken(value);
                _unit = value;
            }
        }

        public ICollection<RangeItemHeaderValue> Ranges => _ranges ??= new UnvalidatedObjectCollection<RangeItemHeaderValue>();

        public RangeHeaderValue()
        {
            _unit = HeaderUtilities.BytesUnit;
        }

        public RangeHeaderValue(long? from, long? to)
        {
            // convenience ctor: "Range: bytes=from-to"
            _unit = HeaderUtilities.BytesUnit;
            Ranges.Add(new RangeItemHeaderValue(from, to));
        }

        private RangeHeaderValue(RangeHeaderValue source)
        {
            Debug.Assert(source != null);

            _unit = source._unit;
            if (source._ranges != null)
            {
                foreach (RangeItemHeaderValue range in source._ranges)
                {
                    this.Ranges.Add(new RangeItemHeaderValue(range));
                }
            }
        }

        public override string ToString()
        {
            var sb = new ValueStringBuilder(stackalloc char[256]);
            sb.Append(_unit);
            sb.Append('=');

            if (_ranges != null)
            {
                bool first = true;
                foreach (RangeItemHeaderValue range in _ranges)
                {
                    if (first)
                    {
                        first = false;
                    }
                    else
                    {
                        sb.Append(", ");
                    }

                    if (range.From.HasValue) sb.AppendSpanFormattable(range.From.GetValueOrDefault());
                    sb.Append('-');
                    if (range.To.HasValue) sb.AppendSpanFormattable(range.To.GetValueOrDefault());
                }
            }

            return sb.ToString();
        }

        public override bool Equals([NotNullWhen(true)] object? obj)
        {
            RangeHeaderValue? other = obj as RangeHeaderValue;

            if (other == null)
            {
                return false;
            }

            return string.Equals(_unit, other._unit, StringComparison.OrdinalIgnoreCase) &&
                HeaderUtilities.AreEqualCollections(_ranges, other._ranges);
        }

        public override int GetHashCode()
        {
            int result = StringComparer.OrdinalIgnoreCase.GetHashCode(_unit);

            if (_ranges != null)
            {
                foreach (RangeItemHeaderValue range in _ranges)
                {
                    result ^= range.GetHashCode();
                }
            }

            return result;
        }

        public static RangeHeaderValue Parse(string input)
        {
            int index = 0;
            return (RangeHeaderValue)GenericHeaderParser.RangeParser.ParseValue(input, null, ref index);
        }

        public static bool TryParse([NotNullWhen(true)] string? input, [NotNullWhen(true)] out RangeHeaderValue? parsedValue)
        {
            int index = 0;
            parsedValue = null;

            if (GenericHeaderParser.RangeParser.TryParseValue(input, null, ref index, out object? output))
            {
                parsedValue = (RangeHeaderValue)output!;
                return true;
            }
            return false;
        }

        internal static int GetRangeLength(string? input, int startIndex, out object? parsedValue)
        {
            Debug.Assert(startIndex >= 0);

            parsedValue = null;

            if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
            {
                return 0;
            }

            // Parse the unit string: <unit> in '<unit>=<from1>-<to1>, <from2>-<to2>'
            int unitLength = HttpRuleParser.GetTokenLength(input, startIndex);

            if (unitLength == 0)
            {
                return 0;
            }

            RangeHeaderValue result = new RangeHeaderValue();
            result._unit = input.Substring(startIndex, unitLength);
            int current = startIndex + unitLength;
            current += HttpRuleParser.GetWhitespaceLength(input, current);

            if ((current == input.Length) || (input[current] != '='))
            {
                return 0;
            }

            current++; // skip '=' separator
            current += HttpRuleParser.GetWhitespaceLength(input, current);

            int rangesLength = RangeItemHeaderValue.GetRangeItemListLength(input, current, result.Ranges);

            if (rangesLength == 0)
            {
                return 0;
            }

            current += rangesLength;
            Debug.Assert(current == input.Length, "GetRangeItemListLength() should consume the whole string or fail.");

            parsedValue = result;
            return current - startIndex;
        }

        object ICloneable.Clone()
        {
            return new RangeHeaderValue(this);
        }
    }
}
